# -*- coding: utf-8 -*-

# ╭━━━╮╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╭╮
# ┃╭━╮┃╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱┃┃
# ┃┃╱╰╋━━┳╮╭┳╮╭┳━━┳━╮╭━╯┃
# ┃┃╱╭┫╭╮┃╰╯┃╰╯┃╭╮┃╭╮┫╭╮┃
# ┃╰━╯┃╰╯┃┃┃┃┃┃┃╭╮┃┃┃┃╰╯┃
# ╰━━━┻━━┻┻┻┻┻┻┻╯╰┻╯╰┻━━╯

"""
Command Library
Help you quickly build command line input and output
Support native Python 3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

usage:

   >>> from Command import command, OperationDict
   >>> import Command
   >>> @command(name="say_python", value_type=str, help="Life is short, you need say python", default="python")
   >>> def say_python():
   >>>      print(OperationDict['say_python'].value)
   >>> Command.run()

    Terminal:
   >>> python pytest.py say_python 人生苦短,我用python
        人生苦短，我用python
   >>> python pytest.py say_python
        python
   >>> python pytest.py help
        Available parameters
            help[None]:you can get some help

            say_python[str]: Life is short, you need say python

2021.11.17 by Evan Ju
"""

import sys

# OperationDict， Operations for loading and reading definitions
# HelpText， It will load all function descriptions
OperationDict = dict()
HelpText = """Available parameters
\thelp[None]:you can get some help
 """


class Operation:
    def __init__(self, name, type, help, default):
        """
        :param name: operation name
        :param type: type of value corresponding to "name"
        :param help: help documentation for using operation
        :param default: default value corresponding to "name"
        """
        self.name = name
        self.type = type
        self.help = help
        self.default = default
        self.func = None
        self.value = None
        self.parent = None
        self.children = set()
        OperationDict[name] = self

    def set_value(self, value):
        """
        set operation value

        :param value: str, int, float, bool
        """
        self.value = value

    def set_function(self, function):
        """
        set function corresponding to operation

        :param function: type of function
        """
        self.func = function

    def execution_function(self):
        """
        execution function
        """
        try:
            self.func()
        except KeyError as e:
            print("ErrorGetKey:Please check operation_Name defined", e)

    def set_parent(self, parent):
        """
        set the parent operation for the operation. If you are a parent operation, the attribute is none
        :param parent: a operation
        """
        self.parent = parent

    def add_child(self, child):
        """
        add sub operation for operation
        :param child: a operation
        """
        self.children.add(child)


# Check the command line for errors
def check_command():
    """
    check whether the command line input meets the requirements

    :return: bool value
    """
    if len(sys.argv) < 2:
        print("You may need help:", sys.argv[0] + ' help')
        return False
    if sys.argv[1] == "help" or sys.argv[1] == "--help":
        print(HelpText)
        sys.exit(1)
    return True


def get_value_to_operation(name, default):
    """
    Find the value corresponding to the operation "name" in the command line input

    Traverse to get the value corresponding to the parameter in the command line
    If there is a value, return the value
    If there is no value, the operation name is returned and if default exist default
     value is returned
    If no False is returned

    :param name: command line input is key or operation
    :param default: type of name is value,
    :return: value found returned, default value returned if not found
    """
    for index in range(len(sys.argv)):
        if "-" not in name and index < len(sys.argv) - 1 and sys.argv[index] == name:
            return sys.argv[index+1]
        if "-" in name and index < len(sys.argv) - 1 and sys.argv[index] == name:
            return " ".join(sys.argv[index+1:len(sys.argv)]).split(" -")[0]
        if sys.argv[index] == name:
            return sys.argv[index]
        if sys.argv[index] == name and default is not False:
            return default
    return None


def check_input_value(name, value, value_type, default, func_name):
    """
    Judge whether the input value conforms to the set parameter type
    :param name: command line input is key or operation
    :param value: value corresponding to "name", if value_type is None value == "name", else value == default
    :param value_type: None, str, int, float,name can have no corresponding value if it's None
    :param default: the default value of "name" you set, if it's not set, default == False<bool>,
                    Indicates that it cannot be empty
    :param func_name: parent operation name, if "name" == function name, func_name == "name" == function name
    :return: end program
    """
    if sys.argv[1] == "help" or sys.argv[1] == "--help":
        return True
    if value is None:
        return False
    value = to_value_type(name, func_name, value, value_type)
    if type(value) is not value_type and value != default and value_type is not None:
        print(f"Command parameter {name} is type of function {func_name}"
              f" is incorrect, not {type(value)} should be {value_type}")
        return sys.exit(1)
    return True


def to_value_type(name, func_name, value, value_type):
    try:
        if value_type is None:
            return value
        value_type_name = value_type.__name__
        _dict = {
            "str": str(value),
            "int": int(value),
            "float": float(value)
        }
        if _dict.get(value_type_name, None):
            if type(value) != value_type:
                return _dict[value_type_name]
    except ValueError:
        if type(value) == value_type:
            return value
        print(f"Command parameter {name} is type of function {func_name}"
              f" is incorrect, not {type(value)} should be {value_type}")
        return sys.exit(1)
    return None


def call_default(operation, name, value, value_type, default):
    return operation.set_value(value)


def call_name(operation, name, value, value_type, default):
    if value_type is None:
        return operation.set_value(True)
    if default:
        return operation.set_value(default)
    return print(f"input of command parameter {name} is not found")


def call_other(operation, name, value, value_type, default):
    if value_type is None:
        return operation.set_value(True)
    return operation.set_value(value_type(value))


def set_operation_value(operation, name, value, value_type, default):
    _dict = {
        default: call_default,
        name: call_name
    }
    if _dict.get(value, None):
        func = _dict[value]
        return func(operation, name, value, value_type, default)
    return call_other(operation, name, value, value_type, default)


def command(name, value_type=None, help="None", default=False):
    """
    Command wrapper

    if default value of value_type is None,Returns true<bool> or false<bool>
    if no type is specified(str, int, float).
    if default value of value_type isn't None,Return command line input value
    or default When you do not enter any value

    Use less default

    :param name: str, operation name, if this operation is necessary or major, please
                 make it the same as the function name
    :param value_type: str, int, float, None
    :param help: str, help documentation
    :param default: default value
    """
    def decorator(func):
        operation = Operation(name, "None" if value_type is None else value_type.__name__, help, default)
        value = get_value_to_operation(name, default)
        if check_input_value(name, value, value_type, default, func.__name__):
            set_operation_value(operation, name, value, value_type, default)
            # 判断是父母操作还是孩子操作
            if name == func.__name__:
                # 父母操作指向了对应的函数
                operation.set_function(func)
                # 寻找其它操作是否是自己的孩子
                for op in OperationDict:
                    op = OperationDict[op]
                    if op.parent == name:
                        operation.add_child(op.name)
                return func
            # 孩子操作设置函数名为父母
            operation.set_parent(func.__name__)
            # 寻找父母
            for op in OperationDict:
                op = OperationDict[op]
                if op.name == operation.parent:
                    op.add_child(operation.name)
        return func
    return decorator


# 选择判断返回自定义函数
def get_define_function_name(text):
    command_functions = {
        "check_command": "check_command",
        "read_all_function": "read_all_function",
        "init": "init",
        "run": "run"
    }
    if command_functions.get(text, None):
        return False
    return text


def init():
    global HelpText
    for operation in OperationDict:
        operation = OperationDict[operation]
        if operation.parent is None:
            HelpText = HelpText + f"\n\t{operation.name}[{operation.type}]: {operation.help}"
            if len(operation.children) > 0:
                HelpText = HelpText + "\n\tOptional parameters:"
                for child in operation.children:
                    child_operation = OperationDict[child]
                    HelpText = HelpText + f"\n\t\t{child_operation.name}[{child_operation.type}]:{child_operation.help}"
    return check_command()


def run():
    """
    program start

    This function must be used
    """
    if init():
        for operation in OperationDict:
            operation = OperationDict[operation]
            if operation.value and operation.func is not None:
                operation.execution_function()